﻿/**
 *       Project: ServerLib
 *     Copyright: Copyright (C) 2008, Rob Kennedy
 *   Create Date: 2008-07-14 10:16 PM
 * Modified Date: 2008-07-14 10:16 PM    RK  Added comments
 *      Comments: Ignore SA1633.
 *      
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace ServerLib.Network.Dns
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using ServerLib.Settings;
        
    /// --------------------------------------------------------------------
    /// Project	 : ServerLib
    /// Class	 : MXHost
    /// Inherits : None.
    /// --------------------------------------------------------------------
    /// <summary>
    ///  The Lookup worker class, pass a domain name and MX host names
    ///  will be returned.
    /// </summary>
    /// <authorname>Rob Kennedy</authorname>
    /// <remarks>
    /// TODO: May need to rewrite this entire class to interact with DNS
    /// servers directly. Windows unmanaged function calls are not desireable.
    /// </remarks>
    /// --------------------------------------------------------------------
    public class MXLookup
    {
        #region Unmanaged Functions
        
        /// <summary>
        /// DnsQuery is used to retrieve Dns Query records via the Windows networking
        /// sub-system.
        /// </summary>
        /// <param name="hostName">Host name to retrieve a record for</param>
        /// <param name="recordType">The record type to retrieve</param>
        /// <param name="lookupOptions">Dns lookup options</param>
        /// <param name="aipServers">Aip Server value</param>
        /// <param name="queryResults">Query results pointer</param>
        /// <param name="reserved">Reserved for future use</param>
        /// <returns>Returns zero on success</returns>
        [DllImport("dnsapi.dll", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern uint DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string hostName, ushort recordType, uint lookupOptions, uint aipServers, ref IntPtr queryResults, uint reserved);

        /// <summary>
        /// Clears the Dns list results from global memory
        /// </summary>
        /// <param name="recordList">the recordList pointer to free</param>
        /// <param name="freeType">The free memory type</param>
        [DllImport("dnsapi.dll", EntryPoint = "DnsRecordListFree", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern void DnsRecordListFree(ref IntPtr recordList, uint freeType);

        #endregion

        #region Private Constants
        
        /// <summary>Constant for Dns query bypass cache option</summary>
        private const uint DnsQueryBypassCache = 8;

        /// <summary>Constant for Dns record type MX</summary>
        private const ushort DnsTypeMX = 15;
        
        #endregion

        #region Private Properties

        /// <summary>Contains the domain name to lookup</summary>
        private string domainName;

        /// <summary>Contains the Dns cache record time-to-live value in minutes</summary>
        private int ttl;

        /// <summary>Contains the collection of MXRecord cache</summary>
        private List<MXCacheDomain> lookupCache = new List<MXCacheDomain>();

        #endregion

        #region Private DNS Structure

        /// <summary>
        /// DNSRecord structure returned from the DnsQuery function
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct DNSRecord
        {
            public IntPtr pointerNext;
            public string domainName;
            public short type;
            public short dataLength;
            public int flags;
            public int ttl;
            public int reserved;
            public IntPtr nameExchange;
            public short preference;
            public short padding;
        }

        #endregion

        #region Constructor methods

        /// <summary>
        /// Public constructor for MXLookup
        /// </summary>
        public MXLookup() : this ("localhost")
        {
        }

        /// <summary>
        /// Public constructor for MXLookup
        /// </summary>
        /// <param name="domainName">The domain name to lookup</param>
        public MXLookup(string domainName)
        {
            this.domainName = domainName;
            this.ttl = 20;
        }
        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the domain name to lookup
        /// </summary>
        public string DomainName
        {
            get 
            { 
                return this.domainName; 
            }
            
            set 
            { 
                this.domainName = value; 
            }
        }

        /// <summary>
        /// Gets or sets the Dns cache record time-to-live value in minutes
        /// </summary>
        public int TTL
        {
            get 
            { 
                return this.ttl; 
            }
            
            set 
            { 
                this.ttl = value; 
            }
        }

        /// <summary>
        /// Gets the MXRecord cache collection
        /// </summary>
        public List<MXCacheDomain> Cache
        {
            get
            {
                return this.lookupCache;
            }
        }

        /// <summary>
        /// Gets a collection of MXRecord results for the specified domain name lookup
        /// </summary>
        public List<MXRecord> MXRecords
        {
            get
            {
                List<MXRecord> resultValue = new List<MXRecord>();
                MXCacheDomain cached = null;
                bool domainExists = false;

                // look through the cache and see if we already have a record for this domain
                foreach (MXCacheDomain dom in this.Cache)
                {
                    if (dom.DomainName.ToLower() == this.domainName.ToLower())
                    {
                        // found cached domain, see if we need to refresh
                        domainExists = true;
                        cached = dom;
                        break;
                    }
                }

                // if cached == null, then not found, or TTL is over so we need to refresh
                if (cached != null ? (cached.LastLookupDateTime.AddMinutes(this.ttl) < DateTime.Now || cached.MXRecords.Count == 0) : true)
                {
                    if (cached == null)
                    {
                        cached = new MXCacheDomain();
                        cached.DomainName = this.domainName.ToLower();
                    }

                    cached.LastLookupDateTime = DateTime.Now;
                    
                    IntPtr ptr1 = IntPtr.Zero;
                    IntPtr ptr2 = IntPtr.Zero;
                    DNSRecord recMX = new DNSRecord();

                    // TODO: Make sure this is platform independant; this is unacceptable.
                    if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                    {
                        uint num1 = DnsQuery(ref this.domainName, DnsTypeMX, DnsQueryBypassCache, 0, ref ptr1, 0);
                        
                        if (num1 == 0)
                        {
                            ptr2 = ptr1;
                            
                            // loop through linked list results; while we still have results, loop...
                            while (!ptr2.Equals(IntPtr.Zero))
                            {
                                // convert our pointer to a DNSRecord structure
                                recMX = (DNSRecord)Marshal.PtrToStructure(ptr2, recMX.GetType());

                                // if it's an MX record, use it
                                if (recMX.type == DnsTypeMX)
                                {
                                    // create new MX record
                                    MXRecord newMX = new MXRecord();
                                    
                                    newMX.HostName = Marshal.PtrToStringAuto(recMX.nameExchange);
                                    newMX.Preference = recMX.preference;

                                    // add record to cache collection
                                    cached.MXRecords.Add(newMX);
                                }

                                // get next pointer if any
                                ptr2 = recMX.pointerNext;
                            }

                            // cleanup pointers
                            DnsRecordListFree(ref ptr2, 0);
                        }
                    }

                    // if we have any MX record results, sort them by preference
                    if (cached.MXRecords.Count > 0)
                    {
                        cached.MXRecords.Sort();
                    }

                    // if domain didn't exist in cache, add it to our cache
                    if (!domainExists)
                    {
                        this.Cache.Add(cached);
                    }
                }

                if (cached != null)
                {
                    resultValue = cached.MXRecords;
                }

                return resultValue;
            }
        }
        #endregion

        //public MXHost GetPrimaryMXHost(string domainName, DomainTable domains)
        //{
        //    MXHost retHost = new MXHost();
        //    DomainName = domainName;
        //    List<MXRecord> hosts = MXRecords;
        //    for (int i = 0; i < hosts.Count; i++)
        //    {
        //        if (i == 0)
        //        {
        //            retHost.hostName = hosts[i].HostName;
        //            retHost.IsLocal = domains.DomainExists(domainName);
        //        }
        //        else
        //            retHost.OptionalHosts.Add(hosts[i].HostName);
        //    }
        //    return retHost;
        //}
    }
}
